#include <webx/route.h>
#include <http/HttpHelper.h>
#include <dbentity/T_XG_USER.h>
#include <dbentity/T_XG_CODE.h>
#include <dbentity/T_XG_NOTE.h>
#include <dbentity/T_XG_PARAM.h>
#include <dbentity/T_XG_DBETC.h>
#include <dbentity/T_XG_TIMER.h>
#include <dbentity/T_XG_GROUP.h>

typedef webx::AccessItem AccessItem;

static thread_local int remotestatus;
static TSMap<string, int> remotepathcostmap;
static HttpServer* app = HttpServer::Instance();

class TimerTask : public WorkItem
{
public:
	string path;
	string timeval;

	void run()
	{
		int status = 0;
		SmartBuffer data = webx::GetRemoteResult(path);

		if (data.isNull())
		{
			status = XG_ERROR;
		}
		else
		{
			string code = JsonElement(data.str()).asString("code");

			if (code.empty())
			{
				int code = webx::GetLastRemoteStatus();

				if (code == 200)
				{
					status = XG_OK;
				}
				else if (code == 404)
				{
					status = XG_UNINSTALL;
				}
				else
				{
					status = XG_ERROR;
				}
			}
			else
			{
				status = stdx::atoi(code.c_str()) >= 0 ? XG_OK : XG_ERROR;
			}
		}

		save(status);
	}
	bool save(int status)
	{
		CT_XG_TIMER tab;

		try
		{
			tab.init(webx::GetDBConnect());
		}
		catch(Exception e)
		{
			return false;
		}

		tab.path = path;
		tab.status = status;
		tab.timeval = timeval;
		tab.statetime.update();

		if (status == XG_UNINSTALL) return Check(path, true) && tab.remove() > 0;

		if (status)
		{
			if (status = tab.update()) return status > 0;
		}
		else
		{
			if (tab.find() && tab.next())
			{
				if (timeval == tab.timeval.val()) return true;

				tab.timeval = timeval;

				return tab.update() > 0;
			}
		}

		return tab.insert() > 0;
	}
	bool init(const string& path, const string& timeval)
	{
		this->timeval = timeval;
		this->path = path;

		return save(0);
	}

	static void Clear(sp<DBConnect> dbconn)
	{
		dbconn->execute("DELETE FROM T_XG_TIMER WHERE STATETIME<?", DateTime(time(NULL) - 3 * 24 * 3600));
	}
	static bool Check(const string& path, bool clear)
	{
		string timeval;

		return Check(path, clear, timeval);
	}
	static bool Check(const string& path, bool clear, string& timeval)
	{
		static map<string, string> pathmap;
		static SpinMutex mtx;
		SpinLocker lk(mtx);

		if (path.front() == '{' && path.back() == '}')
		{
			set<string> delset;

			for (const auto& item : pathmap)
			{
				if (path.find("{" + item.first + "}") == string::npos)
				{
					delset.insert(item.first);
				}
			}

			if (delset.empty()) return true;

			CT_XG_TIMER tab;

			try
			{
				tab.init(webx::GetDBConnect());
			}
			catch(Exception e)
			{
				return false;
			}

			TimerTaskQueue::Instance()->check([&](sp<WorkItem> task){
				const TimerTask* timer = dynamic_cast<TimerTask*>(task.get());

				if (timer) return delset.find(timer->path) == delset.end();

				return true;
			});
	
			for (const string& path : delset)
			{
				pathmap.erase(path);
				tab.path = path;
				tab.remove();
			}

			return true;
		}

		auto filter = [&](sp<WorkItem> task){
			const TimerTask* timer = dynamic_cast<TimerTask*>(task.get());

			if (timer && path == timer->path) return false;

			return true;
		};

		if (clear)
		{
			TimerTaskQueue::Instance()->check(filter);

			pathmap.erase(path);

			return true;
		}

		string& val = pathmap[path];

		if (val == timeval) return false;

		if (timeval.empty())
		{
			timeval = val;

			return false;
		}

		TimerTaskQueue::Instance()->check(filter);

		val = timeval;

		return true;
	}
};

class RouteConfig
{
protected:
	int port;
	bool route;
	string host;
	string version;
	string accessversion;
	mutable SpinMutex mtx;
	map<string, vector<string>> groupmap;
	map<string, vector<HostItem>> hostmap;
	map<string, vector<AccessItem>> accessmap;

	RouteConfig() : route(false), port(0)
	{
	}

public:
	void init()
	{
		yaml::config("app.route.port", port);
		yaml::config("app.route.host", host);

		route = port > 0 && host.length() > 0;

		if (host.empty() && app->getRouteSwitch())
		{
			port = app->getPort();
			host = app->getHost();
		}
	}
	HostItem getHost() const
	{
		SpinLocker lk(mtx);

		return HostItem(host, port);
	}
	HostItem get(const string& path) const
	{
		HostItem item;
		SpinLocker lk(mtx);
		const auto it = hostmap.find(CgiMapData::GetKey(path));

		if (it == hostmap.end()) return item;

		const vector<HostItem>& vec = it->second;

		if (vec.empty()) return item;

		return vec[abs(rand()) % vec.size()];
	}
	vector<HostItem> getList(const string& path) const
	{
		SpinLocker lk(mtx);
		const auto it = hostmap.find(CgiMapData::GetKey(path));

		if (it == hostmap.end()) return vector<HostItem>();

		return it->second;
	}
	bool updateAccess(const HostItem& route)
	{
		map<string, vector<AccessItem>> accessmap;

		auto update = [&](){
			CHECK_FALSE_RETURN(webx::LoadAccessMap(accessmap));

			mtx.lock();

			std::swap(this->accessmap, accessmap);

			mtx.unlock();

			return true;
		};

		if (route.canUse())
		{
			JsonElement json;
			HttpRequest request("exportaccess");

			request.setHeadHost(route.host, route.port);
			request.setHeadValue("If-None-Match", accessversion);

			sp<HttpResponse> response = request.getResponse(route.host, route.port);

			CHECK_FALSE_RETURN(response);

			if (response->getStatus() == 304)
			{
				for (auto& item : this->accessmap)
				{
					vector<AccessItem> vec;

					for (auto& elem : item.second)
					{
						if (elem.isRemote()) vec.push_back(elem);
					}

					if (vec.size() > 0) std::swap(accessmap[item.first], vec);
				}

				return update();
			}

			SmartBuffer buffer = response->getResult();

			CHECK_FALSE_RETURN(buffer.str() && json.init(buffer.str()));

			json = json.get("list");

			CHECK_FALSE_RETURN(json.isArray());

			for (JsonElement item : json)
			{
				string path = item["path"].asString();
				string param = item["param"].asString();
				string grouplist = item["grouplist"].asString();

				accessmap[path].push_back(AccessItem(param, stdx::split(grouplist, ","), true));
			}

			accessversion = response->getHeadValue("ETag");
		}

		return update();
	}
	bool updateRoute(const string& host, int port, bool reloadsystem)
	{
		CHECK_FALSE_RETURN(host.length() > 0 && port > 0);

		HttpRequest request("exportroute");

		request.setHeadHost(host, port);
		request.setParameter("access", CGI_PROTECT);
		request.setHeadValue("If-None-Match", version);

		if (route)
		{
			request.setParameter("clientid", app->getId());
			request.setParameter("clientname", app->getName());
			request.setParameter("clientport", app->getPort());
			request.setParameter("clienthost", app->getHost());
		}

		sp<HttpResponse> response = request.getResponse(host, port);

		CHECK_FALSE_RETURN(response);

		if (response->getStatus() == 304)
		{
			SpinLocker lk(mtx);

			this->host = host;
			this->port = port;

			return true;
		}

		string pathlist;
		JsonElement json;
		map<string, vector<HostItem>> hostmap;
		SmartBuffer buffer = response->getResult();

		CHECK_FALSE_RETURN(buffer.str() && json.init(buffer.str()));

		JsonElement list = json.get("list");

		CHECK_FALSE_RETURN(list.isArray());

		for (JsonElement item : list)
		{
			int weight;
			HostItem host;
			JsonElement hostlist = item["list"];
			string path = item["path"].asString();
			vector<HostItem>& vec = hostmap[path];

			pathlist += "{" + path + "}";

			for (JsonElement data : hostlist)
			{
				weight = data["weight"].asInt();
				host.port = data["port"].asInt();
				host.host = data["host"].asString();

				if (weight < 1) weight = 1;
				if (weight > 9) weight = 9;

				for (int i = 0; i < weight; i++) vec.push_back(host);
			}
		}

		if (pathlist.length() > 0) TimerTask::Check(pathlist, false, pathlist);

		mtx.lock();

		std::swap(this->hostmap, hostmap);

		this->version = response->getHeadValue("ETag");
		this->host = host;
		this->port = port;

		mtx.unlock();

		updateRouteConfig(json, host, reloadsystem);

		return true;
	}
	void setCgiDefaultGroup(const string& path, const string& grouplist)
	{
		if (grouplist.length() > 0)
		{
			string key = CgiMapData::GetKey(path);
			vector<string> vec = stdx::split(grouplist, ",");

			mtx.lock();

			std::swap(groupmap[key], vec);

			mtx.unlock();
		}
	}
	int checkAccess(const string& path, const string& param, const string& grouplist)
	{
		vector<string> vec = stdx::split(grouplist, ",");

		for (const string& group : vec)
		{
			if (group == "root") return XG_OK;
		}

		{
			int res = 0;
			string key = CgiMapData::GetKey(path);
			auto checkDefaultGroup = [&](const string& key, const vector<string>& grouplist){
				auto it = groupmap.find(key);

				if (it == groupmap.end()) return XG_FAIL;

				for (const string& item : it->second)
				{
					if (item == "public") return XG_OK;

					for (const string& group : grouplist)
					{
						if (item == group) return XG_OK;
					}
				}

				return XG_FAIL;
			};

			SpinLocker lk(mtx);
			auto it = accessmap.find(key);

			if (it == accessmap.end()) return checkDefaultGroup(key, vec);

			for (const AccessItem& item : it->second)
			{
				if (res = item.check(param, vec)) return res;
			}

			return checkDefaultGroup(key, vec);
		}
	}
	void updateRouteConfig(JsonElement& json, const string& routehost, bool reloadsystem)
	{
		if (!RedisConnect::CanUse())
		{
			int port = json["redis"]["port"].asInt();

			if (port > 0)
			{
				Socket sock;
				string host = json["redis"]["host"].asString();
				string password = json["redis"]["password"].asString();

				if (host.empty() || host == LOCAL_IP) host = routehost;

				if (sock.connect(host, port))
				{
					RedisConnect::Setup(host, port, password);

					sp<RedisConnect> redis = RedisConnect::Instance();

					if (redis)
					{
						LogTrace(eIMP, "initialize redis[%s:%d] success", host.c_str(), port);

						if (reloadsystem) webx::ReloadSystemConfig();
					}
					else
					{
						LogTrace(eERR, "initialize redis[%s:%d] failed", host.c_str(), port);
					}
				}
			}
		}

		if (!webx::GetLogHost().canUse())
		{
			int port = json["log"]["port"].asInt();
			string host = json["log"]["host"].asString();

			if (port > 0)
			{
				if (host.empty() || host == LOCAL_IP) host = routehost;

				if (webx::SetLogHost(host, port) > 0)
				{
					LogTrace(eINF, "update log host[%s:%d] success", host.c_str(), port);
				}
				else
				{
					LogTrace(eERR, "update log host[%s:%d] failed", host.c_str(), port);
				}
			}
		}
	}

	static RouteConfig* Instance()
	{
		static RouteConfig route;

		return &route;
	}
};

class NotifyItem : public WorkItem
{
public:
	int port;
	string host;
	string path;
	string param;
	string cookie;
	string contype;

public:
	void run()
	{
		HttpRequest request(path);

		request.setPayload(param);
		request.setHeadHost(host, port);

		if (cookie.length() > 0) request.setCookie(cookie);
		if (contype.length() > 0) request.setContentType(contype);

		if (request.getResponse(host, port))
		{
			LogTrace(eINF, "notify[%s:%d][%s] success", host.c_str(), port, path.c_str());
		}
		else
		{
			LogTrace(eERR, "notify[%s:%d][%s] failed", host.c_str(), port, path.c_str());
		}
	}
};

class PingWorkItem : public WorkItem
{
public:
	int port;
	int errcnt;
	int maxcnt;
	string host;

public:
	PingWorkItem()
	{
		errcnt = 0;
		maxcnt = 3;
	}
	bool runnable()
	{
		if (process() >= 0) return true;

		++errcnt;

		return false;
	}
	int process()
	{
		HttpRequest request("getcgilist");

		request.setHeadHost(host, port);
		request.setParameter("system", 1);
		request.setParameter("access", CGI_PROTECT);
		request.setParameter("routehost", app->getHost());
		request.setParameter("routeport", app->getPort());

		long us;
		Timer tr(NULL);
		string content;
		JsonElement json;
		SmartBuffer buffer = request.getResult(host, port);

		us = tr.getTimeGap();

		if (buffer.str() && json.init(buffer.str()) && json["list"].isArray())
		{
			LogTrace(eINF, "ping host[%s:%d] success[%ld]", host.c_str(), port, us);

			content = buffer.str();
		}
		else
		{
			LogTrace(eERR, "ping host[%s:%d] failed", host.c_str(), port);

			if (errcnt < maxcnt) return XG_NETERR;
		}

		while (maxcnt-- > 0)
		{
			string sqlcmd;
			sp<DBConnect> dbconn;

			try
			{
				dbconn = webx::GetDBConnect();
			}
			catch(Exception e)
			{
				Sleep(100);

				continue;
			}

			if (content.empty())
			{
				sqlcmd = "UPDATE T_XG_ROUTE SET PROCTIME=0 WHERE HOST=? AND PORT=?";

				return dbconn->execute(sqlcmd, host, port) < 0 ? XG_FAIL : XG_OK;
			}
			else
			{
				JsonElement item;
				JsonElement json(content);
				JsonElement list = json.get("list");

				for (int i = list.size() - 1; i >= 0; i--)
				{
					item = list.get(i);

					string extdata = item.asString("extdata");

					if (extdata.length() > 0)
					{
						HttpDataNode param;

						param.parse(extdata);

						string path = item.asString("path");
						string delay = param.getValue("timertask");
						string daily = param.getValue("dailytask");
						string timeout = param.getValue("timeout");

						if (timeout.length() > 0)
						{
							webx::SetRemoteProcessTimeout(path, stdx::atoi(timeout.c_str()) * 1000);
						}

						auto checkTimer = [](const string& path, const string& timeval){
							string val = timeval;

							if (TimerTask::Check(path, false, val))
							{
								sp<TimerTask> task = newsp<TimerTask>();

								if (task->init(path, val))
								{
									if (val.find(':') == string::npos)
									{
										int delay = stdx::atoi(val.c_str());

										if (delay > 0)
										{
											TimerTaskQueue::Instance()->push(task, delay * 1000);
										}
										else
										{
											LogTrace(eINF, "create timertask[%s][%s] failed", path.c_str(), val.c_str());

											TimerTask::Check(path, true);
										}
									}
									else
									{
										TimerTaskQueue::Instance()->daily(task, val);
									}
	
									LogTrace(eINF, "create timertask[%s][%s] success", path.c_str(), val.c_str());
								}
								else
								{
									LogTrace(eINF, "create timertask[%s][%s] failed", path.c_str(), val.c_str());

									TimerTask::Check(path, true);
								}
							}
						};

						if (delay.length() > 0)
						{
							int val = stdx::atoi(delay.c_str());

							if (val > 0)
							{
								checkTimer(path, stdx::str(val));
							}
							else
							{
								LogTrace(eINF, "invalid timertask[%s][%s]", path.c_str(), delay.c_str());
							}
						}

						if (daily.length() > 0)
						{
							DateTime dt = DateTime::FromString(DateTime::ToString().substr(0, 11) + daily);

							if (dt.canUse())
							{
								checkTimer(path, dt.getTimeString());
							}
							else
							{
								LogTrace(eINF, "invalid dailytask[%s][%s]", path.c_str(), daily.c_str());
							}
						}
					}
				}

				sqlcmd = "UPDATE T_XG_ROUTE SET PROCTIME=?,CONTENT=?,STATETIME=? WHERE HOST=? AND PORT=?";

				return dbconn->execute(sqlcmd, us, content, DateTime().update(), host, port) < 0 ? XG_FAIL : XG_OK;
			}
		}

		return XG_FAIL;
	}
};

class PingTimerItem : public WorkItem
{
	int index = 0;
	atomic_long utime;
	set<string> hostset;
	mutable SpinMutex mtx;

public:
	void run()
	{
		const static string sqlcmd = "SELECT HOST,PORT FROM T_XG_ROUTE WHERE ENABLED>0 GROUP BY HOST,PORT";

		utime = time(NULL);

		if (app->isActive())
		{
			if (app->getRouteSwitch())
			{
				sp<RowData> row;
				sp<QueryResult> rs;
				sp<DBConnect> dbconn;

				try
				{
					dbconn = webx::GetDBConnect();
				}
				catch(Exception e)
				{
					return;
				}

				if (rs = dbconn->query(sqlcmd))
				{
					int delay = 0;
					set<string> hostset;

					LogTrace(eINF, "start route ping process success");

					while (row = rs->next())
					{
						sp<PingWorkItem> item = newsp<PingWorkItem>();
						
						item->host = row->getString(0);
						item->port = row->getInt(1);
						hostset.insert(item->host);
						stdx::delay(delay, item);
						delay += 50;
					}

					mtx.lock();

					std::swap(this->hostset, hostset);

					mtx.unlock();
				}

				if (index % 100 == 0) TimerTask::Clear(dbconn);

				sp<Session> session = webx::GetLocaleSession("SYSTEM_ROUTELIST");

				if (session) session->clear();
			}

			updateRouteList(true);
			updateAcccessList();

			++index;
		}
	}
	void updateAcccessList()
	{
		HostItem item = RouteConfig::Instance()->getHost();

		if (RouteConfig::Instance()->updateAccess(item))
		{
			LogTrace(eINF, "update access list success");
		}
		else
		{
			LogTrace(eERR, "update access list failed");
		}
	}
	time_t getUpdateTime() const
	{
		return utime;
	}
	void updateRouteList(bool reloadsystem)
	{
		HostItem item = RouteConfig::Instance()->getHost();

		if (item.canUse())
		{
			if (RouteConfig::Instance()->updateRoute(item.host, item.port, reloadsystem))
			{
				LogTrace(eINF, "update route list success");

				utime = time(NULL);
			}
			else
			{
				LogTrace(eERR, "update route list failed");
			}
		}
	}
	bool checkHost(const string& host) const
	{
		SpinLocker lk(mtx);

		return hostset.find(host) != hostset.end();
	}

	static const sp<PingTimerItem>& Instance()
	{
		static sp<PingTimerItem> task = newsp<PingTimerItem>();

		return task;
	}
};

static BOOL NeedUpdateRouteHost()
{
	return RouteConfig::Instance()->getHost().canUse() ? PingTimerItem::Instance()->getUpdateTime() + 30 < time(NULL) : true;
}

static BOOL CheckHost(const char* host)
{
	return host && PingTimerItem::Instance()->checkHost(host);
}

static BOOL InitNewUser(const char* user)
{
	try
	{
		CT_XG_USER tmp;

		tmp.init(webx::GetDBConnect());

		tmp.user = user;

		CHECK_FALSE_RETURN(tmp.find() && tmp.next());

		string dbid = tmp.dbid.val();

		CHECK_FALSE_RETURN(tmp.find("USER='template'") && tmp.next());

		int res;
		CT_XG_NOTE note;
		CT_XG_CODE code;
		CT_XG_PARAM param;
		vector<CT_XG_NOTE> notevec;
		vector<CT_XG_CODE> codevec;
		vector<CT_XG_PARAM> paramvec;
		sp<DBConnect> srconn = tmp.dbid.val().empty() ? tmp.getHandle() : webx::GetDBConnect(tmp.dbid.val());

		if (note.init(srconn) && note.find("USER='template'"))
		{
			while (note.next())
			{
				notevec.push_back(note);
				notevec.back().user = user;
			}
		}
		
		if (code.init(srconn) && code.find("USER='template'"))
		{
			while (code.next())
			{
				codevec.push_back(code);
				codevec.back().user = user;
			}
		}

		if (param.init(srconn) && param.find("ID LIKE 'template.%'"))
		{
			while (param.next())
			{
				paramvec.push_back(param);
				paramvec.back().id = user + param.id.val().substr(8);
			}
		}

		sp<DBConnect> destconn = dbid == tmp.dbid.val() ? srconn : webx::GetDBConnect(dbid);

		for (auto& tab : notevec)
		{
			tab.init(destconn, false);

			for (int i = 0; i < 5; i++)
			{
				tab.id = DateTime::GetBizId();
				if ((res = tab.insert()) >= 0) break;
			}
		}

		for (auto& tab : codevec)
		{
			tab.init(destconn, false);

			for (int i = 0; i < 5; i++)
			{
				tab.id = DateTime::GetBizId();
				if ((res = tab.insert()) >= 0) break;
			}
		}

		for (auto& tab : paramvec)
		{
			tab.init(destconn, false);
			tab.insert();
		}

		return true;
	}
	catch (Exception e)
	{
		return false;
	}
};

static void AsyncInitNewUser(const char* user)
{
	string newuser = user;

	stdx::async([=](){
		try{
			InitNewUser(newuser.c_str());
		}
		catch(Exception e)
		{
			LogTrace(eERR, "initialize user[%s] failed[%d][%s]", newuser.c_str(), e.getErrorCode(), e.getErrorString());
		}
	});
}

static int GetString(const string& val, char* dest, int size)
{
	if (--size > val.length()) size = val.length();

	if (size > 0)
	{
		strcpy(dest, val.c_str());
	}
	else
	{
		*dest = 0;
	}

	return val.length();
};

static int GetParameter(const char* id, char* dest, int size)
{
	sp<Session> session = webx::GetLocaleSession("SYSTEM_PARAMETER", 600);

	if (!session) return XG_SYSERR;

	string val = session->get(id);

	if (val.length() > 0) return GetString(val, dest, size);

	string sqlcmd = "SELECT PARAM FROM T_XG_PARAM WHERE ID=?";

	try
	{
		if (webx::GetDBConnect()->select(val, sqlcmd, id) < 0) return XG_SYSERR;

		session->set(id, val = stdx::translate(val));

		return GetString(val, dest, size);
	}
	catch(Exception e)
	{
		return XG_SYSBUSY;
	}
}

static DBConnectPool* GetDBConnectPool(const char* id)
{
	static SpinMutex mtx;
	static map<string, DBConnectPool*> dbmap;

	char buffer[1024];
	SpinLocker lk(mtx);
	auto it = dbmap.find(id);

	if (it != dbmap.end()) return it->second;

	YAMLoader cfg;
	CT_XG_DBETC tab;
	sp<DBConnect> dbconn;

	try
	{
		dbconn = webx::GetDBConnect();
	}
	catch(Exception e)
	{
		return NULL;
	}

	tab.init(dbconn);
	tab.id = id;

	if (!tab.find())
	{
		LogTrace(eERR, "query database pool[%s] failed", id);

		return NULL;
	}

	if (!tab.next())
	{
		LogTrace(eERR, "database pool[%s] undefined", id);

		return NULL;
	}

	if (tab.enabled.val() <= 0)
	{
		LogTrace(eERR, "database pool[%s] disable", id);

		return NULL;
	}

	cfg.set("database.type", tab.type.val());
	cfg.set("database.host", tab.host.val());
	cfg.set("database.user", tab.user.val());
	cfg.set("database.name", tab.name.val());
	cfg.set("database.port", tab.port.val());
	cfg.set("database.charset", tab.charset.val());
	cfg.set("database.password", tab.password.val());

	DBConnectPool* pool = DBConnectPool::Create(&cfg);

	if (pool == NULL)
	{
		LogTrace(eERR, "create database pool[%s] failed", id);

		return NULL;
	}

	return dbmap[id] = pool;
}

static void SetCgiDefaultGroup(const char* path, const char* grouplist)
{
	if (path && grouplist) RouteConfig::Instance()->setCgiDefaultGroup(path, grouplist);
}

static int CheckAccess(const char* path, const char* param, const char* grouplist)
{
	if (path == NULL) return XG_ERROR;

	int res = RouteConfig::Instance()->checkAccess(path, param ? param : "", grouplist ? grouplist : "");

	if (res == XG_FAIL && app->getCgiAccess(path) == CGI_PUBLIC) return XG_AUTHFAIL;

	return res;
}

static int NotifyHost(const char* host, int port, const char* path, const char* param, const char* contype, const char* cookie)
{
	sp<NotifyItem> item = newsp<NotifyItem>();

	item->port = port;
	item->host = host;
	item->path = path;

	if (param) item->param = param;
	if (cookie) item->cookie = cookie;
	if (contype) item->contype = contype;

	return stdx::async(item) ? XG_OK : XG_SYSBUSY;
}

static int BroadcastHost(const char* path, const char* param, const char* contype, const char* cookie)
{
	int num = 0;
	set<string> hostset;
	vector<HostItem> vec = RouteConfig::Instance()->getList(path);

	for (const HostItem& item : vec)
	{
		if (hostset.insert(item.toString()).second)
		{
			int res = NotifyHost(item.host.c_str(), item.port, path, param, contype, cookie);

			if (res < 0) return res;

			++num;
		}
	}

	return num;
}

static int GetRegCenterHost(char* host, int* port)
{
	HostItem item = RouteConfig::Instance()->getHost();

	if (item.host.empty()) return XG_NOTFOUND;

	strcpy(host, item.host.c_str());
	*port = item.port;

	return XG_OK;
}

static int UpdateRouteList(const char* host, int port)
{
	return RouteConfig::Instance()->updateRoute(host, port, true) ? XG_OK : XG_SYSERR;
}

static int GetRouteHost(const char* path, char* host, int* port)
{
	HostItem item = RouteConfig::Instance()->get(path);

	if (item.host.empty()) return XG_NOTFOUND;

	strcpy(host, item.host.c_str());
	*port = item.port;

	return XG_OK;
}

static int GetLastRemoteStatus()
{
	return remotestatus;
}

static int GetRemoteProcessTimeout(const char* path)
{
	int timeout = SOCKET_CONNECT_TIMEOUT;

	if (path) remotepathcostmap.get(CgiMapData::GetKey(path), timeout);

	return timeout;
}

static void SetRemoteProcessTimeout(const char* path, int timeout)
{
	if (path) remotepathcostmap.set(CgiMapData::GetKey(path), timeout < SOCKECT_RECVTIMEOUT ? SOCKECT_RECVTIMEOUT : timeout);
}

static SmartBuffer GetRemoteResult(const char* path, const char* param, const char* contype, const char* cookie)
{
	int port;
	char host[64];
	HttpRequest request(path);
	sp<HttpResponse> response;
	int timeout = GetRemoteProcessTimeout(request.getPath().c_str());

	if (param) request.setPayload(param);
	if (cookie && *cookie) request.setCookie(cookie);
	if (contype && *contype) request.setContentType(contype);

	if (GetRouteHost(request.getPath().c_str(), host, &port) < 0)
	{
		response = app->getLocaleResult(request, timeout);
	}
	else
	{
		sp<Socket> sock = SocketPool::Connect(host, port);

		if (sock)
		{
			request.setHeadHost(host, port);
		}
		else
		{
			LogTrace(eERR, "request remote[%s] failed", path);

			remotestatus = XG_SENDFAIL;

			return SmartBuffer();
		}

		response = request.getResponse(sock, true, timeout);
	}

	if (response)
	{
		remotestatus = response->getStatus();

		return response->getResult();
	}

	LogTrace(eERR, "request remote[%s] failed", path);

	remotestatus = XG_RECVFAIL;

	return SmartBuffer();
}

EXTERN_DLL_FUNC int HttpPluginInit(IHttpServer* app)
{
	Process::SetObject("HTTP_CHECKHOST_FUNC", (void*)CheckHost);
	Process::SetObject("HTTP_NOTIFY_HOST_FUNC", (void*)NotifyHost);
	Process::SetObject("HTTP_CHECK_ACCESS_FUNC", (void*)CheckAccess);
	Process::SetObject("HTTP_GET_PARAMETER_FUNC", (void*)GetParameter);
	Process::SetObject("HTTP_GET_ROUTE_HOST_FUNC", (void*)GetRouteHost);
	Process::SetObject("HTTP_BROADCAST_HOST_FUNC", (void*)BroadcastHost);
	Process::SetObject("HTTP_GET_REMOTE_RESULT_FUNC", (void*)GetRemoteResult);
	Process::SetObject("HTTP_UPDATE_ROUTE_LIST_FUNC", (void*)UpdateRouteList);
	Process::SetObject("HTTP_GET_DBCONNECT_POOL_FUNC", (void*)GetDBConnectPool);
	Process::SetObject("HTTP_ASYNC_INIT_NEW_USER_FUNC", (void*)AsyncInitNewUser);
	Process::SetObject("HTTP_GET_REG_CENTER_HOST_FUNC", (void*)GetRegCenterHost);
	Process::SetObject("HTTP_SET_CGI_DEFAULT_GROUP_FUNC", (void*)SetCgiDefaultGroup);
	Process::SetObject("HTTP_GET_LAST_REMOTE_STATUS_FUNC", (void*)GetLastRemoteStatus);
	Process::SetObject("HTTP_NEED_UPDATE_ROUTE_HOST_FUNC", (void*)NeedUpdateRouteHost);
	Process::SetObject("HTTP_SET_REMOTE_PROCESS_TIMEOUT_FUNC", (void*)SetRemoteProcessTimeout);
	Process::SetObject("HTTP_GET_REMOTE_PROCESS_TIMEOUT_FUNC", (void*)GetRemoteProcessTimeout);

	sp<PingTimerItem> timer = PingTimerItem::Instance();
	RouteConfig::Instance()->init();
	timer->updateRouteList(false);
	timer->updateAcccessList();
	stdx::timer(5000, timer);

	return XG_OK;
}

class RouteModule : public webx::ProcessBase
{
protected:
	int process();
};

HTTP_WEBCGI(CGI_PRIVATE, "${filename}")
DEFINE_HTTP_CGI_EXPORT_FUNC(RouteModule)

int RouteModule::process()
{
	param_name_string(host);

	return simpleResponse(CheckHost(host.c_str()) ? XG_OK : XG_NOTFOUND);
}